package panda.natalia.gothread.suspend.instrument;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.Frame;
import panda.natalia.gothread.suspend.Instrumented;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

import static panda.natalia.gothread.suspend.GlobalDefine.*;
import static panda.natalia.gothread.suspend.instrument.MethodDatabase.*;

/**
 * a simple framework of java fiber
 *
 * @Author: scpanda2007
 * @Date: 2019/11/29 11:51
 */
public class InstrumentMethod {
    private static final boolean optimiztionDisabled = false;
    private static final boolean HANDLE_PROXY_INVOCATIONS = true;

    private static final int NUM_LOCALS = 3;
    private static final int ADD_OPERANDS = 6;

    private final MethodDatabase db;

    private final String sourceName;
    private final String className;

    private final MethodNode mn;
    private final Frame[] frames;

    private final int lvarStack; // gothread 保存环境的 stack 记录指针
    private final int lvarResumed; // 用于恢复现场时 进行判断 的 状态值, 是第一次运行 还是 重新唤醒gothread 后的状态
    private final int lvarInvocationReturnvalue;

    private final int firstLocal;

    private FrameInfo[] codeBlocks = new FrameInfo[32]; // 应该是为try catch 做的切分块 进行的block 收集和新增
    private int numCodeBlock;

    private int additionalLocals; // 为做状态保存等植入代码 新追加的变量 用户不可见
    private int maxRefSlots;

    private boolean warnedAboutMonitors;
    private int warnedAboutBlocking; // 按理说使用gothread后, 用户将不应该使用 block 性质的操作

    private boolean callsSuspendableSupers;

    private int startSourceLine = -1;
    private int endSourceLine = -1;
    private int[] suspCallsSourceLines = new int[8];
    private String[] suspCallsNames = new String[0];
    private int[] suspCallsBcis = null;

    InstrumentMethod(MethodDatabase db, String sourceName, String className, MethodNode mn) throws AnalyzerException {
        this.db = db;
        this.sourceName = sourceName;
        this.className = className;
        this.mn = mn;

        try{
            Analyzer a = new TypeAnalyzer(db);
            this.frames = a.analyze(className, mn);
            this.lvarStack = mn.maxLocals;
            this.lvarResumed = mn.maxLocals + 1; // 追加一个状态变量
            this.lvarInvocationReturnvalue = mn.maxLocals + 2;  // 追加一个返回值记录变量

            this.firstLocal = ((mn.access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC) ? 0 : 1; // 如果是非静态, 第一个变量需要除去 调用者
            this.maxRefSlots = 0;
        }catch (UnsupportedOperationException ex){
            throw new AnalyzerException(null, ex.getMessage(), ex);
        }

    }

    public static void emitInstrumentedAnn(MethodDatabase db, MethodVisitor mv, MethodNode mn, String sourceName, String className, boolean skip, int startSourceLine, int endSourceLine, int[] suspCallSites, String[] suspCallSiteNames, int[] postInstrOffsets) {
        final StringBuffer sb = db.isDebug() ? new StringBuffer() : null;
        final AnnotationVisitor instrumentedAV = mv.visitAnnotation(INSTRUMENTED_DESC, true);
        if(sb != null)
            sb.append("@").append(Instrumented.class.getSimpleName()).append("(");

        instrumentedAV.visit(Instrumented.FIELD_NAME_METHOD_OPTIMIZED, skip);
        if(sb != null)
            sb.append(Instrumented.FIELD_NAME_METHOD_OPTIMIZED + "=").append(skip).append(", ");

        instrumentedAV.visit(Instrumented.FIELD_NAME_METHOD_START, startSourceLine);
        if(sb != null)
            sb.append(Instrumented.FIELD_NAME_METHOD_START + "=").append(startSourceLine).append(", ");

        instrumentedAV.visit(Instrumented.FIELD_NAME_METHOD_END, endSourceLine);
        if(sb != null)
            sb.append(Instrumented.FIELD_NAME_METHOD_END + "=").append(endSourceLine).append(", ");

        if(suspCallSites != null){
            final AnnotationVisitor linesAV = instrumentedAV.visitArray(Instrumented.FIELD_NAME_SUSPENDABLE_CALL_SITES);
            if(sb != null)
                sb.append(Instrumented.FIELD_NAME_SUSPENDABLE_CALL_SITES + "=" + Arrays.toString(suspCallSites) +"");
            Arrays.stream(suspCallSites).forEach(line -> linesAV.visit(null, line));
            linesAV.visitEnd();
            sb.append(", ");
        }

        if(suspCallSiteNames != null){
            final AnnotationVisitor linesAV = instrumentedAV.visitArray(Instrumented.FIELD_NAME_SUSPENDABLE_CALL_SITE_NAMES);
            if(sb != null)
                sb.append(Instrumented.FIELD_NAME_SUSPENDABLE_CALL_SITE_NAMES + "=" + Arrays.toString(suspCallSiteNames) +"");
            Arrays.stream(suspCallSiteNames).forEach(name -> linesAV.visit(null, name));
            linesAV.visitEnd();
            sb.append(", ");
        }

        if(postInstrOffsets != null){
            final AnnotationVisitor linesAV = instrumentedAV.visitArray(Instrumented.FIELD_NAME_SUSPENDABLE_CALL_SITES_OFFSETS_AFTER_INSTR);
            if(sb != null)
                sb.append(Instrumented.FIELD_NAME_SUSPENDABLE_CALL_SITE_NAMES + "=" + Arrays.toString(postInstrOffsets) +"");
            Arrays.stream(postInstrOffsets).forEach(instr -> linesAV.visit(null, instr));
            linesAV.visitEnd();
        }

        instrumentedAV.visitEnd();

        if(sb!=null) sb.append(")");

        db.log(LogLevel.DEBUG, "Annotating method %s:%s#%s%s with %s", sourceName, className, mn.name, mn.desc, sb);
    }

    private static class FrameInfo {

    }

    public void accept(MethodVisitor mv, boolean hasAnnotation){
        db.log(LogLevel.INFO, "Instrumenting method %s:%s#%s%s", sourceName, className, mn.name, mn.desc);

        if(mn.name.charAt(0) == '<') // <init>
            throw new UnableToInstrumentException("spacial method", className, mn.name, mn.desc);

        collectCallsites();
    }

    /**
     * 收集所有的发起函数的调用的地方
     */
    private void collectCallsites() {
        if(suspCallsBcis == null){
            suspCallsBcis = new int[8];
            final int numIns = mn.instructions.size();
            int currSourceLine = -1;
            int count = 0;
            final Set<String> callSiteNames = new HashSet<>();
            // TODO: 为什么不是 frames.length 呢, 也就是说 instructions 是 frames 的一小部分 待测试
            /*
             * public void test(int i){
             *     System.out.println("this is " + i);
             * }
             *
             *
             * public test(I)V
             *         L0
             * LINENUMBER 19 L0
             * GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
             * NEW java/lang/StringBuilder
             * DUP
             * INVOKESPECIAL java/lang/StringBuilder.<init> ()V
             * LDC "this is "
             * INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
             * ILOAD 1
             * INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
             * INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
             * INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
             *         L1
             * LINENUMBER 20 L1
             *         RETURN
             * L2
             * LOCALVARIABLE this Lpanda/natalia/gothread/suspend/instrument/TestCode; L0 L2 0
             * LOCALVARIABLE i I L0 L2 1
             * MAXSTACK = 3
             * MAXLOCALS = 2
            */
            for (int i = 0; i < numIns; i++){
                final Frame f = frames[i];
                if(f != null){
                    final AbstractInsnNode in = mn.instructions.get(i);
                    if(in.getType() == AbstractInsnNode.LINE){
                        final LineNumberNode lnn = (LineNumberNode) in;
                        currSourceLine = lnn.line;
                        if(startSourceLine == -1 || currSourceLine < startSourceLine)
                            startSourceLine = currSourceLine; // 定位实际代码的首行位置
                        if(endSourceLine == -1 || currSourceLine > endSourceLine)
                            endSourceLine =currSourceLine; // 定位尾行
                        // 以上两个判断似乎看起来是  instrumentions 的顺序和实际代码行的顺序是 不一致的吗 ?
                        // 例如虚拟机会做优化导致代码乱序 ?
                    } else if (in.getType() == AbstractInsnNode.METHOD_INSN ||
                        in.getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN){
                        if(isSuspendableCall(db, in)){
                            if(count >= suspCallsBcis.length)
                                suspCallsBcis = Arrays.copyOf(suspCallsBcis, suspCallsBcis.length * 2);
                            if(count >= suspCallsSourceLines.length)
                                suspCallsSourceLines = Arrays.copyOf(suspCallsSourceLines, suspCallsSourceLines.length * 2);
                            suspCallsBcis[count] = i;
                            suspCallsSourceLines[count] = currSourceLine;
                            callSiteNames.add(getSuspendableCallName(in));
                            count++;
                        }else
                            possiblyWarnAbountBlocking(in);
                    }
                }
            }

            // 修剪掉 多余的 空间, 因为是原生的 int 类型数组, 它不想用 List 来做, 而是自己写 数组 并手动进行数组长度扩增和裁剪
            if(count < suspCallsSourceLines.length)
                suspCallsSourceLines = Arrays.copyOf(suspCallsSourceLines, count);
            if(count < suspCallsBcis.length)
                suspCallsBcis = Arrays.copyOf(suspCallsBcis, count);

            suspCallsNames = callSiteNames.toArray(new String[0]);

        }
    }

    private void possiblyWarnAbountBlocking(final AbstractInsnNode ain) {
        if(ain instanceof MethodInsnNode){
            final MethodInsnNode min = (MethodInsnNode) ain;
            int blockingId = blockingCallIdx(min);
            if(blockingId >= 0 && !isAllowedToBlock(className, mn.name)){
                int mask = 1 << blockingId;
                if(!db.isAllowBlocking()){
                    throw new UnableToInstrumentException("blocking call to " + min.owner + "#" + min.name + min.desc, className, mn.name, mn.desc);
                }else if((warnedAboutBlocking & mask) == 0){ // 防止重复警告
                    warnedAboutBlocking |= mask;
                    db.log(LogLevel.INFO.WARNING, "Method %s:%s#%s%s contains potentially blocking call to " + min.owner + "#" + min.name + min.desc, sourceName, className, mn.name, mn.desc);
                }
            }
        }
    }

    private String getSuspendableCallName(AbstractInsnNode in) {
        return (in.getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN ? "-" : getMethodOwner1(in))
                + "." + getMethodName(in) + getMethodDesc(in);
    }

    private String getMethodDesc(AbstractInsnNode in) {
        return in instanceof MethodInsnNode ? ((MethodInsnNode) in).desc
                : in instanceof InvokeDynamicInsnNode ? ((InvokeDynamicInsnNode) in).desc : null;
    }

    private String getMethodName(AbstractInsnNode in) {
        return in instanceof MethodInsnNode ? ((MethodInsnNode) in).name
                : in instanceof InvokeDynamicInsnNode ? ((InvokeDynamicInsnNode) in).name : null;
    }

    private String getMethodOwner1(AbstractInsnNode in) {
        return in instanceof MethodInsnNode ? ((MethodInsnNode) in).owner
                : in instanceof InvokeDynamicInsnNode ? ((InvokeDynamicInsnNode) in).bsm.getOwner() : null;
    }

    private boolean isSuspendableCall(MethodDatabase db, AbstractInsnNode in) {
        final int type = in.getType();
        String owner;
        String name;
        String desc;
        if(type == AbstractInsnNode.METHOD_INSN){
            final MethodInsnNode min = (MethodInsnNode) in;
            owner = min.owner; // 函数属于哪个类的, 使用的internal name, 就是jvm 字节码全路径的class名
            name = min.name;
            desc = min.desc;
        }else if(type == AbstractInsnNode.INVOKE_DYNAMIC_INSN){
            final InvokeDynamicInsnNode idd = (InvokeDynamicInsnNode) in;
            owner = idd.bsm.getOwner();
            name = idd.name;
            desc = idd.desc;
        }else{
            throw new RuntimeException("Not a call: " + in);
        }

        return isSuspendableCall(db, type, in.getOpcode(), owner, name, desc);
    }

    // 这个方法似乎只是明确排除掉 一定不是 suspendable 的方法
    public static boolean isSuspendableCall(MethodDatabase db, int type, int opcode, String owner, String name, String desc) {
        boolean susp = true;
        if(type == AbstractInsnNode.METHOD_INSN){
            // ！的这些方法意味着 可能是 suspendable 的
            if(!isSyntheticAccess(owner, name)
               && !isReflectInvocation(owner, name)
               && !isMethodHandleInvocation(owner, name)
               && !isInvocationHandlerInvocation(owner, name)){
                MethodDatabase.SuspendableType st = db.isMethodSuspendable(owner, name, desc, opcode);

                if(st == MethodDatabase.SuspendableType.NON_SUSPENDABLE)
                    susp = false;
            }
        } else if (type == AbstractInsnNode.INVOKE_DYNAMIC_INSN){
            /**
             * {@link panda.natalia.gothread.suspend.instrument.codesample.TestLambda}
             *  此处应指的动态函数绑定的过程, 而非函数调用的过程
             */
            if (owner.equals("java/lang/invoke/LambdaMetafactory")) // lambda
                susp = false;
        } else
            susp = false;

        return susp;
    }


}
